// Copyright 2007 Google Inc. All Rights Reserved.
// Author: agl@imperialviolet.org (Adam Langley)
//
// Copyright (C) 2007 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <usb.h>

#include <arpa/inet.h>

#include <scsi/sg.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

#define REQUEST_SENSE_SIZE 20
#ifndef SG_FLAG_MMAP_IO
#define SG_FLAG_MMAP_IO 4
#endif

#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define u64 uint64_t

#include "kvs70xx.h"


#define VENDOR_ID       0x04da
#define KVS7065_ID       0x1002
#define KVS7075_ID       0x100e

static const unsigned kMaxBuffer = 0x10000;

// -----------------------------------------------------------------------------
// This is a series of utility functions for dealing with SCSI tape devices.
//
// See http://www.tldp.org/HOWTO/SCSI-Generic-HOWTO/index.html
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// Dump the given requestsense buffer to stderr in a, hopefully, useful format.
// The requestsense buffer should result from a failed SCSI operation
// -----------------------------------------------------------------------------
void scsi_request_sense_dump(void *irequestsense)
{
	// see http://www.t10.org/lists/asc-num.htm
	const u8 *requestsense = (u8 *) irequestsense;
	fprintf(stderr, "Request failed: ");
	for (int i = 0; i < REQUEST_SENSE_SIZE; ++i) {
		fprintf(stderr, "%02x ", requestsense[i]);
	}
	fprintf(stderr, "\n");
	fprintf(stderr, "ASC/ASCQ: %02x %02x\n",
		requestsense[12], requestsense[13]);
}

#if 0
// -----------------------------------------------------------------------------
// Returns non-zero if the given error is transient (e.g. due to the device not
// being ready
// -----------------------------------------------------------------------------
static int scsi_transient_error(void *irequestsense)
{
	// see http://www.t10.org/lists/asc-num.htm
	const u8 *requestsense = (u8 *) irequestsense;
	const u8 asc = requestsense[12];

	if (asc == 0x28 || asc == 0x29 || asc == 0x04)
		return 1;

	return 0;
}
#endif
// -----------------------------------------------------------------------------
// Returns the SCSI error code from a request-sense-buffer.
// See: http://www.t10.org/lists/asc-num.htm
// -----------------------------------------------------------------------------
u16 scsi_error_code(const u8 * requestsense)
{
	// see http://www.t10.org/lists/asc-num.htm
	return (requestsense[12] << 8) | requestsense[13];
}

#define COMMAND_BLOCK	1
#define DATA_BLOCK	2
#define RESPONSE_BLOCK	3

#define COMMAND_CODE	0x9000
#define DATA_CODE	0xb000
#define RESPONSE_CODE	0xa000
#define STATUS_SIZE 4

struct bulk_header {
	u32 length;
	u16 type;
	u16 code;
	u32 transaction_id;
}__attribute__((packed));
#define GOOD 0
#define CHECK_CONDITION 2

typedef enum {
	CMD_NONE = 0,
 CMD_IN = 0x81,		/* scanner to pc */
 CMD_OUT = 0x02		/* pc to scanner */
} CMD_DIRECTION;		/* equals to endpoint address */

#define RESPONSE_SIZE	0x12
#define MAX_CMD_SIZE	12
struct cmd {
	const unsigned char *cmd;
	int cmd_size;
	void *data;
	int data_size;
	int dir;
};
struct response {
	int status;
	unsigned char data[RESPONSE_SIZE];
};

#define REQUEST_SENSE          0x03
static int usb_send_command(struct usb_dev_handle *uh, struct cmd *c, struct response *r,
			    void *buf, int timeout)
{
	struct bulk_header *h = (struct bulk_header *) buf;
	u8 resp[sizeof(*h) + STATUS_SIZE];
	size_t sz = sizeof(*h) + MAX_CMD_SIZE;
	memset(h, 0, sz);
	h->length = htonl(sz);
	h->type = htons(COMMAND_BLOCK);
	h->code = htons(COMMAND_CODE);
	memcpy(h + 1, c->cmd, c->cmd_size);
	//if(!timeout)
		timeout = 10000;
		//	usb_clear_halt(uh, CMD_IN);
		//	usb_clear_halt(uh, CMD_OUT);
	sz = usb_bulk_write(uh, CMD_OUT, (char *)h, sz, timeout);
	
	if (sz < 0){
		return -1;
	}

	if (c->dir == CMD_IN) {
		sz = sizeof(*h) + c->data_size;

		sz = usb_bulk_read(uh, CMD_IN, (char *)h, sz, timeout);
		c->data = h + 1;

		if (sz < sizeof(*h)) {
			usb_clear_halt(uh, CMD_IN);
			usb_clear_halt(uh, CMD_OUT);
			r->status = CHECK_CONDITION;
			c->data_size = 0;
			return 0;

		}
		
		c->data_size = sz - sizeof(*h);

	} else if (c->dir == CMD_OUT) {

		sz = sizeof(*h) + c->data_size;

		memset(h, 0, sizeof(*h));
		h->length = htonl(sizeof(*h) + c->data_size);
		h->type = htons(DATA_BLOCK);
		h->code = htons(DATA_CODE);
		memcpy(h + 1, c->data, c->data_size);

		sz = usb_bulk_write(uh, CMD_OUT, (char *)h, sz, timeout);
		if (sz < 0){
			return sz;
		}
	}
	sz = sizeof(resp);
	sz = usb_bulk_read(uh, CMD_IN, (char *)resp, sz, timeout);
	if (sz != sizeof(resp)){
		return -1;
	}
	r->status = ntohl(*((u32 *) (resp + sizeof(*h))));
	return 0;
}

// -----------------------------------------------------------------------------
// Perform a SCSI command
//   fd: file descriptor of the tape device
//   direction: either SG_DXFER_TO_DEV or SG_DXFER_FROM_DEV
//   command: SCSI command bytes
//   command_length: length, in bytes, of command
//   data: payload data. If NULL, the mmap buffer is used
//   data_length: length of the payload data
//   requestsense: (output) resulting error buffer
//   timeout: timeout in milliseconds. Must be > 0
//
// Returns:
//   0 on success
//   1 in the case of an ioctl error
//   2 in the case of a SCSI error
// -----------------------------------------------------------------------------
static int
send_command(int fd, int direction,
	     const void *command, unsigned command_length,
	     void *data, unsigned data_length,
	     void *requestsense, int timeout)
{
	struct usb_dev_handle *uh = (struct usb_dev_handle *)fd;
	int st = 0;
	u8 *bb = alloca(sizeof(struct bulk_header) +
			(data_length > MAX_CMD_SIZE?
			data_length:MAX_CMD_SIZE));
	struct response r = {};
	struct cmd c = {
		.cmd = command,
		.cmd_size = command_length,
  		.dir = !data || !data_length ? CMD_NONE: 
			direction == SG_DXFER_TO_DEV
				 ? CMD_OUT : CMD_IN,
 		.data = data,
		.data_size = data_length
	};
	memset(requestsense, 0, RESPONSE_SIZE);
	
	st = usb_send_command(uh, &c, &r, bb, timeout);
	if (st){
		return 1;
	}
	if(c.dir == CMD_IN)
		memcpy(data, c.data, c.data_size);

	if (r.status) {
		u8 b[sizeof(struct bulk_header) + RESPONSE_SIZE];
		u8 cmd[6]={REQUEST_SENSE, 0,0,0, RESPONSE_SIZE};
		struct cmd c2 = {
			.cmd = cmd,
			.cmd_size = 6,
			.dir = CMD_IN,
			.data_size = RESPONSE_SIZE,
		};

		st = usb_send_command(uh, &c2, &r, b, timeout);
		if (st || !c2.data_size){
			return 1;
		}
		memcpy(requestsense, b + sizeof(struct bulk_header),
		       RESPONSE_SIZE);

	return 2;

	}

	return 0;
}

// -----------------------------------------------------------------------------
// KVS70XX specific function. See the header file for comments...

void kvs70xx_window_init(struct kvs70xx_window *window)
{
	memset(window, 0, sizeof(struct kvs70xx_window));

	window->composition = KVS70XX_COMPOSITION_COLOUR;
	window->bpp = 24;
	window->xres = window->yres = 300;	// 300 dpi
	window->number_of_pages_to_scan = 0xff;	// all pages
	window->emphasis = 0xf0;	// No conversion
	window->document_size = 7;	// US letter
	window->double_feed_detector = 1;
	window->subsample = 3;	// 4:2:2 JPEG subsampling

	// US letter sizes
	window->document_width = window->width = 8.5 * 1200;
	window->document_length = window->length = 11 * 1200;

	window->compression_type = 0x81;	// JPEG
	window->compression_argument = 85;	// quality 85

}

static int
kvs70xx_window_serialise(u8 * output,
			  const struct kvs70xx_window *window)
{
	unsigned j = 0;
#define U8(x) output[j++] = window->x;
#define U16(x) do { u16 t = htons(window->x); memcpy(output + j, &t, 2); j += 2; } while (0);
#define U32(x) do { u32 t = htonl(window->x); memcpy(output + j, &t, 4); j += 4; } while (0);
#define U8I(x) output[j++] = x;

	U8I(0);
	U8I(0);
	U16(xres);
	U16(yres);
	U32(x1);
	U32(y1);
	U32(width);
	U32(length);
	U8(brighness);
	U8(threshold);
	U8(contrast);
	U8(composition);
	U8(bpp);
	U16(halftone_pattern);
	U8I(window->reverse_image ? 0x80 : 0);
	U16(bit_ordering);
	U8(compression_type);
	U8(compression_argument);
	j += 6;
	U8I(0);
	U8I(window->flatbed << 7 |
	    window->stop_on_skew << 4 |
	    window->disable_buffering << 3 |
	    window->continue_on_double_feed << 0);
	U8(mirror_image);
	U8(emphasis);
	U8(gamma_correction);
	U8I(window->multi_colour_drop_out << 7 |
	    window->lamp << 4 | window->double_feed_sensitivity << 0);
	U8I(window->remove_moire << 6 |
	    window->subsample << 4 | window->colour_match << 0);
	U8(document_size);
	U32(document_width);
	U32(document_length);
	U8I(window->ahead_disable << 7 |
	    window->deskew << 5 |
	    window->double_feed_detector << 4 |
	    window->full_size_scan << 2 |
	    window->feed_slow << 1 | window->remove_shadow << 0);
	U8(number_of_pages_to_scan);
	U8(threshold_mode);
	U8(separation_mode);
	U8(standard_white_level);
	U8I(window->blackwhite_noise_reduction << 7 |
	    window->noise_reduction << 0);
	U8I(window->manual_feed_mode << 6 |
	    window->additional_space_top << 5 |
	    window->additional_space_bottom << 4 |
	    window->detect_separation_sheet << 3 |
	    window->halt_at_separation_sheet << 2 |
	    window->detect_control_sheet << 1);
	U8(stop_mode);
	//U8(red_chroma);
	//U8(blue_chroma);

#undef U8
#undef U16
#undef U32
#undef U8I

	return j;
}

#define WINDOW_SIZE 64

int
kvs70xx_set_windows(int fd,
		     const struct kvs70xx_window *window,
		     char duplex, u8 * requestsense)
{
	u8 windowbytes[6 + 2 + WINDOW_SIZE];
	memset(windowbytes, 0, sizeof(windowbytes));

	const int bytes_written =
	    kvs70xx_window_serialise(windowbytes + 8, window);
	if (bytes_written != WINDOW_SIZE)
		abort();

	const u16 length = htons(WINDOW_SIZE);
	memcpy(windowbytes + 6, &length, sizeof(length));
	static const int transfer_length = sizeof(windowbytes);

	const u8 command[] = { 0x24, 0, 0, 0, 0, 0, transfer_length >> 16,
		transfer_length >> 8, transfer_length, 0
	};
	const int r =
	    send_command(fd, SG_DXFER_TO_DEV, command, sizeof(command),
			 windowbytes, transfer_length, requestsense, 0);

	if (r)
		return r;

	if (duplex) {
		windowbytes[8] = 0x80;
		return send_command(fd, SG_DXFER_TO_DEV, command,
				    sizeof(command), windowbytes,
				    transfer_length, requestsense, 0);
	}

	return 0;
}

int
kvs70xx_reset_windows(int fd, u8 * requestsense)
{


	const u8 command[] = { 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	const int r =
	    send_command(fd, SG_DXFER_TO_DEV, command, sizeof(command),
			 NULL, 0, requestsense, 0);

	if (r)
		return r;
	return 0;
}

int kvs70xx_scan(int fd, u8 * requestsense)
{
	static const u8 command[] = { 0x1b, 0, 0, 0, 0, 0 };

	return send_command(fd, SG_DXFER_TO_DEV, command, sizeof(command),
			    NULL, 0, requestsense, 0);
}

int
get_data_buffer_status(int fd, u8 * window_id, u32 * length,
		       u8 * requestsense)
{
	u8 buffer[12];
	static const u8 command[] =
	    { 0x34, 0, 0, 0, 0, 0, 0, 0, sizeof(buffer), 0 };
	const int r =
	    send_command(fd, SG_DXFER_FROM_DEV, command, sizeof(command),
			 buffer, sizeof(buffer), requestsense, 0);
	if (r)
		return r;

	*window_id = buffer[4];
	const u32 length_be = ((u32) buffer[9]) << 16 |
	    ((u32) buffer[10]) << 8 | ((u32) buffer[11]);
	*length = length_be;

	return 0;
}

int test_unit_ready(int fd, u8 * requestsense)
{
	static const u8 command[] = { 0, 0, 0, 0, 0, 0 };
	return send_command(fd, SG_DXFER_FROM_DEV, command,
			    sizeof(command), NULL, 0, requestsense, 0);
}

int
kvs70xx_read(int fd, u8 type, u8 q1, u8 q2, u8 * buffer, u32 length,
	      u8 * requestsense)
{
	const u8 command[] =
	    { 0x28, 0, type, 0, q1, q2, length >> 16, length >> 8, length,
   0 };

	return send_command(fd, SG_DXFER_FROM_DEV, command,
			    sizeof(command), buffer, length, requestsense,
			    0);
}

#define KVS70XX_READ_IMAGE 0
#define KVS70XX_READ_PICTURE_ELEMENT_SIZE 0x80
#define KVS70XX_READ_SUPPORT 0x93

int
kvs70xx_picture_size(int fd, u8 page, u8 back, u32 * width, u32 * height,
		      u8 * requestsense)
{
	u8 buffer[16];
	memset(buffer, 0, sizeof(buffer));

	const int r =
	    kvs70xx_read(fd, KVS70XX_READ_PICTURE_ELEMENT_SIZE, page,
			  back ? 0x80 : 0, buffer, sizeof(buffer),
			  requestsense);
	if (r)
		return r;
	const u32 width_be = *((u32 *) buffer);
	const u32 height_be = *((u32 *) & buffer[4]);

	if (width)
		*width = ntohl(width_be);
	if (height)
		*height = ntohl(height_be);

	return 0;
}

#define UNSAFE_MIN(x, y) x < y ? x : y

// -----------------------------------------------------------------------------
// Poll the scanner until it has data to send us
// -----------------------------------------------------------------------------
int kvs70xx_data_buffer_wait(int fd, int *back, u8 * requestsense)
{
	u32 length;
	u8 window_id;

	for (;;) {
		if (get_data_buffer_status
		    (fd, &window_id, &length, requestsense)) {
			return 1;
		}

		if (length)
			break;
		usleep(5000);
	}
	*back = window_id ? 1:0;
	return 0;
}

int kvs70xx_read_data(int fd, u8 page, u8 back, u8 * buffer,
		       unsigned blen, unsigned *result, char *eof, char *ili,
		       u8 * requestsense)
{
	const unsigned length = UNSAFE_MIN(kMaxBuffer, blen);
	if (kvs70xx_read
	    (fd, KVS70XX_READ_IMAGE, page, back ? 0x80 : 0, buffer,
	     length, requestsense)) {
		if (requestsense[0] == 0xf0) {
			// End of Medium and ILI
			const u32 delta =
			    ntohl(*((u32 *) & requestsense[3]));
			*result = length - delta;
			*eof = (requestsense[2] & (1<<6)) ? 1:0;
			*ili = (requestsense[2] & (1<<5)) ? 1:0;
			return 0;
		}
		return 1;
	}
	*result = length;
	*eof = 0;

	return 0;
}

int kvs70xx_stop(int fd, u8 * requestsense)
{
	static const u8 command[] = { 0xe1, 0, 0x8b, 0, 0, 0, 0, 0, 0, 0 };
	return send_command(fd, SG_DXFER_TO_DEV, command, sizeof(command),
			    NULL, 0, requestsense, 0);
}

static void print_dev(struct usb_device *dev)
{
	struct usb_device_descriptor *d = &dev->descriptor;
	fprintf(stderr,"name :%s, vendor: 0x%x, product 0x%x\n", dev->filename,
	        d->idProduct, d->idProduct);
}

int kvs70xx_list_devices()
{
	struct usb_bus *bus;
	struct usb_device *dev = NULL;
	/* Use libusb */
	usb_init();
	if (!usb_get_busses()) {
		usb_find_busses();
		usb_find_devices();
	}
	int cnt = 0;
	for (bus = usb_get_busses(); bus; bus = bus->next) {
		for (dev = bus->devices; dev; dev = dev->next) {
			if (dev->config
			    && dev->descriptor.idVendor == VENDOR_ID) {
				if (dev->descriptor.idProduct == KVS7075_ID
				    || dev->descriptor.idProduct == KVS7065_ID) {
					print_dev(dev);
						cnt++;
				}
			}
		}
	}
	if (cnt == 0)
		fprintf(stderr, "No devices found\n");
	return 0;
}

int kvs70xx_document_exist(int fd, uint8_t * requestsense)
{
	u8 d[6];
	static const u8 command[] = { 0x28, 0, 0x81, 0, 0, 0,
		 0, 0, 6, 0 };

	if(send_command(fd, SG_DXFER_FROM_DEV, command,
			    sizeof(command), d, sizeof(d),
				   requestsense, 0))
		return -1;
	return !(d[0] & 0x20);
}

int kvs70xx_open(const char *name)
{
	uint8_t requestsense[KVS70XX_REQUEST_SENSE_SIZE];
	struct usb_bus *bus;
	struct usb_device *dev = NULL;
	usb_dev_handle *h;
	int i;
	/* Use libusb */
	if (!usb_get_busses()) {
		usb_find_busses();
		usb_find_devices();
	}

	for (bus = usb_get_busses(); bus; bus = bus->next) {
		for (dev = bus->devices; dev; dev = dev->next) {
			if (dev->config
			    && dev->descriptor.idVendor == VENDOR_ID) {
				if (dev->descriptor.idProduct == KVS7065_ID
				    || dev->descriptor.idProduct == KVS7075_ID) {
					if(name && strcmp(name, dev->filename))
						continue;
					goto found;
				}
			}
		}
	}
	return -1;
found:
	h = usb_open(dev);
	if(!h){
		perror("Can not open device");
		return -1;
	}

	/* Claim USB interface 0 */
	if (usb_claim_interface(h, 0)) {
		perror("Can not claim interface");
		usb_close(h);
		return -1;
	}
	usb_clear_halt(h, CMD_IN);
	usb_clear_halt(h, CMD_OUT);
	for (i = 0; i < 3; i++) {
		if (test_unit_ready((int)h, requestsense)) {
			usb_release_interface(h, 0);
			usb_close(h);
			h = usb_open(dev);
			if(!h)
				break;
			if (usb_claim_interface(h, 0))
				break;
			usb_clear_halt(h, CMD_IN);
			usb_clear_halt(h, CMD_OUT);
		} else
			goto success;
	}
	usb_release_interface(h, 0);
	usb_close(h);
	return -1;
success:
	return (int)h;
}

void kvs70xx_close(int fd)
{
	struct usb_dev_handle *uh = (struct usb_dev_handle *)fd;
	usb_release_interface(uh, 0);
	usb_close(uh);
}

const char *kvs70xx_strerror(u8 * requestsense)
{
	const u16 error = scsi_error_code(requestsense);
	const u8 sense = requestsense[2] & 0x0f;

	if (sense == 0) {
		switch (error) {
		case 0x0000:
			return "No sense";
		}
	}

	if (sense == 2) {
		switch (error) {
		case 0x0000:
			return "Not ready";
		case 0x0401:
			return
			    "Logical unit is in process of becoming ready";
		case 0x0480:
			return "Document lead door open";
		case 0x0481:
			return "Document discharge door open";
		case 0x0482:
			return "Post imprinter door open";
		case 0x8001:
			return "Scanner stopped";
		case 0x8002:
			return "ADF stopped";
		}
	}

	if (sense == 3) {
		switch (error) {
		case 0x3a00:
			return "Out of paper";
		case 0x8001:
			return "Jammed at document lead";
		case 0x8002:
			return "Jammed at document discharge 1";
		case 0x8003:
			return "Jammed at document discharge 2";
		case 0x8004:
			return "Document internal rest";
		case 0x8006:
			return "Jammed at document feed 1";
		case 0x8007:
			return "Jammed at document feed 2";
		case 0x8008:
			return "Jammed at document feed 3";
		case 0x8009:
			return "Jammed at document feed 4";
		case 0x800a:
			return "Skew error";
		case 0x800b:
			return "Minimum media error";
		case 0x800c:
			return "Media length error";
		case 0x800d:
			return "Double feed error";
		case 0x800e:
			return "Barcode error";
		}
	}

	if (sense == 4) {
		switch (error) {
		case 0x0880:
			return "Internal parameter error";
		case 0x0881:
			return "Internal DMA error";
		case 0x0882:
			return "Internal command error";
		case 0x8083:
			return "Internal communication error";
		case 0x4480:
			return "Internal RAM error";
		case 0x4481:
			return "Internal EEPROM error";
		case 0x4482:
			return "FPGA error";
		case 0x4700:
			return "SCSI parity error";
		case 0x8001:
			return "Lamp failure with regular temperature";
		case 0x8002:
			return "Document size detect error";
		case 0x8004:
			return "Document hopper error";
		case 0x8005:
			return "Document sensor adjust error";
		}
	}

	if (sense == 5) {
		switch (error) {
		case 0x1a00:
			return "Parameter list length error";
		case 0x2000:
			return "Invalid command op code";
		case 0x2400:
			return "Invalid field in CDB";
		case 0x2500:
			return "Logical unit not supported";
		case 0x2600:
			return "Invalid field in parameter list";
		case 0x2c01:
			return "Too many windows";
		case 0x2c02:
			return "Invalid window combination";
		case 0x2c80:
			return "Out of memory";
		case 0x2c81:
			return "No back scanning unit";
		case 0x2c82:
			return "No imprinter unit";
		case 0x2c83:
			return "Pointer position error";
		case 0x2c84:
			return "Out of scanning page limit";
		case 0x2c85:
			return "Out of scanning length limit";
		case 0x2c86:
			return "Out of scanning resolution limit";
		case 0x2c87:
			return "Out of scanning line cycle limit";
		case 0x3d00:
			return "Invalid bits in identity message";
		}
	}

	if (sense == 6) {
		switch (error) {
		case 0x2900:
			return "Unit attention";
		case 0x1b00:
			return "Sync data transfer error";
		case 0x4300:
			return "Message error";
		case 0x4900:
			return "Invalid message error";
		case 0x8001:
			return "Image data transfer error";
		}
	}

	return NULL;
}

int kvs70xx_init()
{
	/* Initialize USB */
	usb_init();
	return 0;
}
